#pragma once

#include "Sock.hpp"
#include "Epoll.hpp"
#include "Log.hpp"
#include "Util.hpp"
#include "Protocol.hpp"
#include <unordered_map>

class Tcpserver
{
    static const int defaultport = 8080;      // 默认端口号
    static const int default_maxevents = 256; // epoll就绪集合的默认大小

public:
    // 构造
    Tcpserver(int port = defaultport, int maxevents = default_maxevents)
        : _sk(port), _ep(maxevents)
    {
    }

    // 析构
    ~Tcpserver()
    {
        // 释放所有Connect对象，并关闭所有文件描述符
        for (auto &kv : _cons)
        {
            delete kv.second;
            close(kv.first);
        }
    }

    // 初始化
    void Init()
    {
        // 1、创建套接字
        _sk.Create();
        log(DEBUG, "sock create success");
        // 2、绑定套接字
        _sk.Bind();
        log(DEBUG, "sock bind success");
        // 3、监听套接字
        _sk.Listen();
        log(DEBUG, "sock listen success");
        // 4、创建Epoll模型
        _ep.Create();
        log(DEBUG, "epoll create success, epfd:%d", _ep.getfd());
        // 5、将监听套接字添加进等待集合当中
        int listensock = _sk.getsock();
        struct epoll_event ev;
        ev.events = EPOLLIN | EPOLLET; // 设置关心读事件以及ET模式
        ev.data.fd = listensock;
        if (!Set_watch(listensock, &ev)) // 添加失败
        {
            log(FATAL, "watch listensock failed");
            exit(WATCH_LISTENSOCK_FAILED);
        }

        log(DEBUG, "watch listensock success,listensock:%d", listensock);
    }

private:
    Tcpserver(const Tcpserver &) = delete;            // 删除拷贝构造
    Tcpserver &operator=(const Tcpserver &) = delete; // 删除赋值重载

    // 将文件描述符添加进Epoll模型
    bool Set_watch(int sock, struct epoll_event *event)
    {
        // 1、设置文件描述符为非阻塞
        if (!Setunblock(sock))
        {
            log(ERROR, "set unblock failed,sock:%d", sock);
            return false;
        }

        log(DEBUG, "set unblock success, sock:%d", sock);

        // 2、为文件描述符创建connect对象并初始化,然后添加到Connect集合中
        Connect *cptr = new Connect(sock);
        _cons.insert(make_pair(sock, cptr));

        // 3、将文件描述符添加进Epoll模型中
        if (_ep.Control(EPOLL_CTL_ADD, sock, event) < 0)
        {
            log(ERROR, "epoll add failed,sock:%d", sock);
            return false;
        }

        return true;
    }

    // 处理异常的文件描述符
    void Excepter(int fd)
    {
        // 1、先将fd从Epoll模型中移除
        _ep.Control(EPOLL_CTL_DEL, fd, nullptr);

        // 2、将fd从文件描述符集合中移除，并释放对应的Content对象
        auto pos = _cons.find(fd);
        if (pos != _cons.end()) // fd存在于集合当中
        {
            delete pos->second; // 释放Content对象
            _cons.erase(pos);   // 从_cons中移除
        }

        // 3、关闭文件描述符
        close(fd);
    }

    // 检查Connect中是否含有完整的请求;如果有，就处理请求
    void handle_requests(Connect *cont)
    {
        int start = 0; // 下一个报文的起始位置
        while (true)   // 处理所有的请求报文
        {
            // 1、先寻找请求报文中的行分隔符
            size_t pos = cont->_inbuffer.find(LINE_SEP, start);
            if (pos == string::npos) // 不存在行分隔符
            {
                log(DEBUG, "no more request_1");
                break;
            }

            // 2、找到了行分隔符，提取行分隔符之前的正文长度，然后计算当前接收缓冲区中是否有一个完整报文
            string textlen_str = cont->_inbuffer.substr(start, pos - start); // 正文长度(字符串形式)
            int textlen = stoi(textlen_str);                                 // 正文长度(整型形式)
            int req_len = textlen_str.size() + 2 * LEN_LINE_SEP + textlen;   // 请求长度

            if (cont->_inbuffer.size() - start < req_len) // 剩余部分没有一个完整的请求
            {
                log(DEBUG, "no more request_2");
                break;
            }

            // 3、剩余部分还有完整请求，截取请求中的正文部分
            string req_text = cont->_inbuffer.substr(pos + LEN_LINE_SEP, textlen); // 请求正文
            // 4、将请求正文反序列化为一个请求对象,并对请求对象进行计算，将计算的结果生成响应正文
            request req;                    // 请求对象
            string resp_text;               // 响应正文
            string resp_str;                // 完整的响应报文
            if (!req.deserialize(req_text)) // 反序列化失败，正文格式错误
            {
                resp_text = req_text + " 格式错误"; // 响应正文
                cout << resp_text << endl;
                resp_text += ", 输入标准: x op y";
            }
            else // 反序列化成功,对请求对象进行计算，并将计算的结果构建成响应对象
            {
                respond resp;         // 响应对象
                calculate(req, resp); // 计算、生成响应
                // 将响应对象序列化成字符串形式的响应正文
                resp_text = resp.serialize();
            }

            // 5、给响应正文添加报头，形成完整的响应报文
            resp_str = enlength(resp_text);
            // 6、将响应报文添加进发送缓冲区
            cont->_outbuffer += resp_str;

            // 7、更新下一个请求的起始位置
            start += req_len;
        }

        // 8、将接收缓冲区start之前的数据全部移除
        cont->_inbuffer.erase(0, start);

        // 9、将发送缓冲区的数据发送给客户端
        Sender(cont->_fd);
    }

    // 从监听套接字中获取链接
    void Accepter()
    {
        // 可能收到了多个链接，循环读取所有的链接
        while (true)
        {
            log(DEBUG, "ready to accept");
            string clientip, clientport;
            int sock = _sk.Accept(clientip, clientport); // 非阻塞读取

            if (sock >= 0) // 获取链接成功，将文件描述符添加进Epoll模型
            {
                log(DEBUG, "accept a new content,sock:%d", sock);

                // 设置文件描述符为关心读事件、以及ET模式
                struct epoll_event ev;
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = sock;

                if (!Set_watch(sock, &ev)) // 添加失败
                {
                    log(ERROR, "set_watch failed,sock:%d", sock);
                    Excepter(sock);
                }

                // 添加成功，继续读取下一个链接
                log(DEBUG, "watch a new sock success,sock:%d", sock);
            }
            else // 信号中断/未就绪/读取出错
            {
                log(ERROR, "accept new content failed");

                // 根据全局错误码 errno来分情况处理
                if (errno & EINTR) // 信号中断
                {
                    log(DEBUG, "reason:SIG interruptted");
                    continue;
                }
                else if ((errno & EAGAIN) || (errno & EWOULDBLOCK)) // 读事件未就绪
                {
                    log(DEBUG, "reason:listensock is not ready, stop accepting");
                    break;
                }
                else // 读取出错
                {
                    log(FATAL, "reason:sock accept failed");
                    exit(SOCK_ACCEPT_FAILED);
                }
            }
        }
    }

    // 从sock中读取数据
    void Receiver(int sock)
    {
        // 循环读取所有数据
        char buffer[1024];
        while (true)
        {
            log(DEBUG, "ready to reveive");
            int n = recv(sock, buffer, sizeof(buffer) - 1, 0); // 非阻塞式读取
            if (n > 0)                                         // 读取成功
            {
                buffer[n] = 0; // 在读取到的数据后面添加\0
                log(DEBUG, "receive success, the message:%s", buffer);

                _cons[sock]->_inbuffer += buffer; // 将读取到的数据追加到sock的接收缓冲区

                // 检查sock的接收缓冲区中是否有完整的请求报文。如果有，就处理请求报文，并返回响应
                log(DEBUG, "it is going to handle_requests");
                handle_requests(_cons[sock]);
            }
            else if (n == 0) // 写端已关闭
            {
                log(DEBUG, "client quit,stop recveving");
                Excepter(sock);
                break;
            }
            else // 信号中断/未就绪/读取出错
            {
                log(ERROR, "receive err");
                // 根据全局错误码 errno来分情况处理
                if (errno & EINTR) // 信号中断
                {
                    log(DEBUG, "reason:SIG interruptted");
                    continue;
                }
                else if ((errno & EAGAIN) || (errno & EWOULDBLOCK)) // 读事件未就绪
                {
                    log(DEBUG, "reason:sock is not ready, stop receiving");
                    break;
                }
                else // 读取出错
                {
                    log(ERROR, "reason:receive failed,close sock");
                    Excepter(sock);
                    break;
                }
            }
        }
    }



    // 将Connect发送缓冲区中的数据全部发送到 sock中
    void Sender(int sock)
    {
        // 当前sock的写事件有4种情况
        // 情况一：没有关心sock的写事件、但sock的写事件是就绪的，直接发送可以发送成功
        // 清苦二：没有关心sock的写事件、且sock的写事件未就绪，直接发送会失败
        // 情况三：关心sock的写事件、且sock的写事件是就绪的，直接发送可以发送成功
        // 清苦四：关心sock的写事件、但sock的写事件未就绪，直接发送会失败

        // 不管是哪种情况，先发了再说，发送失败再做处理
        while (true)
        {
            log(DEBUG, "ready to send");
            int n = send(sock, _cons[sock]->_outbuffer.c_str(), _cons[sock]->_outbuffer.size(), 0);

            if (n > 0) // 发送成功
            {
                log(DEBUG, "send success");
                _cons[sock]->_outbuffer.erase(0, n);
                if (_cons[sock]->_outbuffer.empty())
                {
                    log(DEBUG, "the outbuffer is empty, stop sending");
                    break;
                }
            }
            else // 信号中断/写事件未就绪/发送出错
            {
                log(ERROR, "send err");
                if (errno & EINTR) // 信号中断
                {
                    log(DEBUG, "reason:sig interruptted");
                    continue;
                }
                else if ((errno & EAGAIN) || (errno & EWOULDBLOCK)) // 写事件未就绪
                {
                    log(DEBUG, "reason:sock is not ready, stop sending");
                    break;
                }
                else // 发送出错
                {
                    log(ERROR, "send failed,close sock");
                    Excepter(sock);
                    break;
                }
            }
        }

        //根据发送缓冲区中是否还有数据剩余，重新设置对sock事件的关心
        struct epoll_event ev;
        ev.data.fd = sock;
        //数据全部发送成功，可以认为sock的写事件依旧是就绪的，下次可以直接发送， 暂时不需要关心sock的写事件
        if (_cons[sock]->_outbuffer.empty())
        {
            //修改sock的事件为关心读事件、以及ET模式
            ev.events = EPOLLIN | EPOLLET;
            _ep.Control(EPOLL_CTL_MOD, sock, &ev);
        }
        else//数据没法送完，则当前sock的写事件一定是不就绪的，必须设置对sock写事件的关心
        {
            // 修改sock的事件为关心读事件、关心写事件、以及ET模式
            ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
            _ep.Control(EPOLL_CTL_MOD, sock, &ev);
        }

    }

    // 处理就绪事件
    void handerevents(int n)
    {
        const struct epoll_event *events = _ep.getlist(); // 就绪列表
        for (int i = 0; i < n; i++)
        {
            int sock = events[i].data.fd;
            size_t event = events[i].events;
            log(DEBUG, "it is going to hander sock:%d", sock);

            // 检查是否有异常事件。如果有，将异常事件转化成读写时间，将来读写出错时再统一处理
            if ((event & EPOLLERR) || (event & EPOLLHUP))
                event |= (EPOLLIN | EPOLLOUT);

            // 处理读事件
            if ((_cons.find(sock) != _cons.end()) && (event & EPOLLIN))
            {
                if (sock == _sk.getsock()) // listensock读事件就绪，获取新的链接
                    Accepter();
                else // 其他链接读事件就绪，读取数据
                    Receiver(sock);
            }

            // 处理写事件
            if ((_cons.find(sock) != _cons.end()) && (event & EPOLLOUT))
                Sender(sock);
        }
    }

public:
    // 启动
    void Start()
    {
        // 监视所有文件描述符，处理所有的就绪时间
        while (true)
        {
            int timeout = 1000; // 等待时间
            int n = _ep.wait(1000);

            if (n > 0) // 有n个文件描述符就绪,处理就序列表的前n个文件描述符
            {
                log(DEBUG, "%d socks has been ready,it is time to hander", n);
                handerevents(n);
            }
            else if (n == 0) // 没有文件描述符就绪，等到超时
                log(DEBUG, "timeout...");
            else // 等待出错
            {
                log(FATAL, "epoll wait failed");
                exit(EPOLL_WAIT_FAILED);
            }

            sleep(1);
        }
    }

private:
    Sock _sk;                            // 套接字模型
    Epoll _ep;                           // epoll模型(ET模式，需要将所有文件描述符设为非阻塞)
    unordered_map<int, Connect *> _cons; // Connect集合
};